/*
 * 1. 创建一个 JFrame 框架实例对象
 * 2. 创建左侧的音乐名称列表并渲染
 * 3. 创建 16*16 的音乐复选框并渲染
 * 4. 创建右侧的按钮并实现其按钮逻辑
 */

import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.*;

class BeatBox {
  JFrame frame;
  ArrayList<JCheckBox> checkboxList;
  Sequencer sequencer;
  Sequence sequence;
  Track track;

  String[] instrumentNames = {
    "Bass Drum", "Closed Hi-Hat", "Open Hi-Hat", "Acoustic Snare", "Crash Symbal", "Hand Clap", "High Tom", "Hi Bongo", 
    "Maracas", "Whistle", "Low Conga", "Cowbell", "Vibraslap", "Low-mid Tom", "High Agogo", "Open Hi Conga"
  };
  int[] instrument = {35, 42, 46, 38, 49, 39, 50, 60, 70, 72, 64, 56, 58, 47, 67, 63};
  
  public static void main(String[] args) {
    new BeatBox().buildGUI();
  }


  /** 创建 GUI 图形操作界面 */
  public void buildGUI() {
    checkboxList = new ArrayList<JCheckBox>();
    
    frame = new JFrame("Cyber BeatBox");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置用户在此框架上启动“关闭”时默认执行的操作。 

    BorderLayout borderLayout = new BorderLayout();
    JPanel mainPanel = new JPanel(borderLayout); // 使用指定的布局管理器创建一个新的缓冲JPanel 
    mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

    Box nameBox = new Box(BoxLayout.Y_AXIS); // 创建一个 Box ，它沿着指定的轴显示其组件。 
    for (int i = 0; i < instrumentNames.length; i++) {
      nameBox.add(new Label(instrumentNames[i]));
    }
    mainPanel.add(BorderLayout.WEST, nameBox);

    GridLayout gridLayout = new GridLayout(16, 16); // 创建具有指定行数和列数的网格布局。
    gridLayout.setVgap(1); // 将组件之间的垂直间距设置为指定值。 
    gridLayout.setHgap(2); // 将组件之间的水平间距设置为指定值。 
    JPanel gridPanel = new JPanel(gridLayout);
    for (int i = 0; i < instrument.length * instrumentNames.length; i++) {
      JCheckBox checkBox = new JCheckBox();
      checkBox.setSelected(false);
      checkboxList.add(checkBox);
      gridPanel.add(checkBox);
    }
    mainPanel.add(BorderLayout.CENTER, gridPanel);

    Box buttonBox = new Box(BoxLayout.Y_AXIS);
    JButton start = new JButton("开始");
    JButton stop = new JButton("暂停");
    JButton tempoUp = new JButton("加快节奏");
    JButton tempoDown = new JButton("放慢节奏");
    JButton save = new JButton("保存");
    JButton restore = new JButton("恢复");
    JButton clear = new JButton("清空");
    JButton sendMsg = new JButton("发送信息");
    start.addActionListener(new StartListener());
    stop.addActionListener(new StopListener());
    tempoUp.addActionListener(new TempoUpListener());
    tempoDown.addActionListener(new TempoDownListener());
    save.addActionListener(new SaveListener());
    restore.addActionListener(new RestoreListener());
    clear.addActionListener(new ClearListener());
    sendMsg.addActionListener(new SendMsgListener());
    buttonBox.add(start);
    buttonBox.add(stop);
    buttonBox.add(tempoUp);
    buttonBox.add(tempoDown);
    buttonBox.add(save);
    buttonBox.add(restore);
    buttonBox.add(clear);
    buttonBox.add(sendMsg);
    mainPanel.add(BorderLayout.EAST, buttonBox);

    frame.getContentPane().add(BorderLayout.CENTER, mainPanel);
    frame.setBounds(50, 50, 600, 500); // 移动并调整此组件的大小。 
    frame.pack(); // 使此窗口的大小适合其子组件的首选大小和布局。调用此方法后，在 setBounds 中设置的 width, height 将失效 
    frame.setVisible(true); // 显示或隐藏此 Window取决于参数的值 b 。 

    midiInit();
  }

  /** “开始”按钮的点击事件 */
  public class StartListener implements ActionListener {
    public void actionPerformed(ActionEvent a) {
      System.out.println("开始");
      buildTrackAndStart();
    }
  }

  /** “暂停”按钮的点击事件 */
  public class StopListener implements ActionListener {
    public void actionPerformed(ActionEvent a) {
      System.out.println("暂停");
      sequencer.stop();
    }
  }

  /** “加快节奏”按钮的点击事件 */
  public class TempoUpListener implements ActionListener {
    public void actionPerformed(ActionEvent a) {
      System.out.println("加快节奏");
      float tempoFactor = sequencer.getTempoFactor();
      // sequencer.setTempoFactor((float) (tempoFactor * 1.03));
      sequencer.setTempoFactor((float) (tempoFactor * 1.5));
      buildTrackAndStart();
    }
  }

  /** “放慢节奏”按钮的点击事件 */
  public class TempoDownListener implements ActionListener {
    public void actionPerformed(ActionEvent a) {
      System.out.println("放慢节奏");
      float tempoFactor = sequencer.getTempoFactor();
      // sequencer.setTempoFactor((float) (tempoFactor * .97));
      sequencer.setTempoFactor((float) (tempoFactor * .5));
      buildTrackAndStart();
    }
  }

  /** “保存”按钮的点击事件 */
  public class SaveListener implements ActionListener {
    public void actionPerformed(ActionEvent a) {
      System.out.println("保存");
      boolean[] checkboxState = new boolean[(instrument.length * instrumentNames.length)];
      for (int i = 0; i < instrument.length * instrumentNames.length; i++) {
        JCheckBox checkBox = (JCheckBox) checkboxList.get(i);
        if (checkBox.isSelected()) {
          checkboxState[i] = true;
        }
      }
      try {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.showOpenDialog(frame);
        FileOutputStream fs = new FileOutputStream(fileChooser.getSelectedFile());
        ObjectOutputStream os = new ObjectOutputStream(fs);
        os.writeObject(checkboxState);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  /** “恢复”按钮的点击事件 */
  public class RestoreListener implements ActionListener {
    public void actionPerformed(ActionEvent a) {
      System.out.println("恢复");
      boolean[] checkboxState = null;
      try {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.showOpenDialog(frame);
        FileInputStream fs = new FileInputStream(fileChooser.getSelectedFile());
        ObjectInputStream os = new ObjectInputStream(fs);
        checkboxState = (boolean[]) os.readObject();
      } catch (Exception e) {
        e.printStackTrace();
      }
      for (int i = 0; i < checkboxState.length; i++) {
        JCheckBox checkbox = (JCheckBox) checkboxList.get(i);
        if (checkboxState[i]) {
          checkbox.setSelected(true);
        } else {
          checkbox.setSelected(false);
        }
      }
      sequencer.stop();
      // buildTrackAndStart();
    }
  }

  /** “清空”按钮的点击事件 */
  public class ClearListener implements ActionListener {
    public void actionPerformed(ActionEvent a) {
      // checkboxList = new ArrayList<JCheckBox>();
      sequencer.stop();
      sequence.deleteTrack(track);
      for (int i = 0; i < (instrumentNames.length * instrument.length); i++) {
        JCheckBox checkBox = (JCheckBox) checkboxList.get(i);
        checkBox.setSelected(false);
      }
    }
  }

  /** “发送信息”按钮的点击事件 */
  public class SendMsgListener implements ActionListener {
    public void actionPerformed(ActionEvent a) {
      System.out.println("点击发送按钮");
    }
  }

  public void midiInit() {
    try {
      sequencer = MidiSystem.getSequencer();
      sequencer.open();
      sequence = new Sequence(Sequence.PPQ, 4);
      track = sequence.createTrack();
      sequencer.setTempoFactor(120); // 按照提供的因素来调整音序器的实际播放速度。
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /** 创造歌曲并播放音乐 */
  public void buildTrackAndStart() {
    int[] trackList = null;
    sequence.deleteTrack(track);
    track = sequence.createTrack();

    for (int i = 0; i < instrument.length; i++) {
      trackList = new int[16];
      int key = instrument[i];

      for (int j = 0; j < instrumentNames.length; j++) {
        JCheckBox checkBox = (JCheckBox) checkboxList.get(j + (16 * i));
        if (checkBox.isSelected()) {
          trackList[j] = key;
        } else {
          trackList[j] = 0;
        }
      }

      makeTracks(trackList);
      track.add(makeEvent(176, 1, 127, 0, 16));
    }

    track.add(makeEvent(192, 9, 1, 0, 15));
    try {
      sequencer.setSequence(sequence);
      sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
      sequencer.start();
      sequencer.setTempoInBPM(120);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public void makeTracks(int[] list) {
    for (int i = 0; i < list.length; i++) {
      int key = list[i];
      if (key != 0) {
        track.add(makeEvent(144, 9, key, 100, i));
        track.add(makeEvent(128, 9, key, 100, i + 1));
      }
    }
  }

  public MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
    MidiEvent event = null;
    try {
      ShortMessage message = new ShortMessage();
      message.setMessage(comd, chan, one, two); // 设置通道消息的短消息参数，最多可以包含两个数据字节。
      event = new MidiEvent(message, tick);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return event;
  }
}